[cpp]资源生命周期管理类:enable_shared_from_this
enable_shared_from_this
模板类能够帮助我们轻松的用对象在其方法中获取指向对象的shared_ptr
, 从而在并行编程中安全的管理资源的生命周期, 避免跨线程调用中资源的提前释放导致程序出错的危害.
我们在之前简单学习了基于RAII的资源管理类的原理和实现, 并实现了一个简易的shared_ptr
类来安全的管理资源. shared_ptr
模板类使用共享的Control Block
来管理资源的生命周期, Control Block
记录了引用计数、weak
引用计数和其他必要的信息来管理资源. 当使用raw
指针构造一个shared_ptr
对象时, 新的Control Block
就会被创建. 因此为了保证只有唯一一个Control Block
来管理资源, 必须用已有的shared_ptr对象
使用拷贝构造\拷贝赋值的方式创建新的shared_ptr对象
. 从而避免资源被多次释放造成的程序出错.
下面给出一个典型的错误使用场景:
1 |
|
因此我们必须避免同一个资源被多个shared_ptr
对象管理, 这会造成程序出错.
对象的生命周期
在并行(异步)编程中, 一个线程可能会调用其他线程的函数异步的完成某些任务, 而该任务依赖于当前线程所用的对象资源, 这种情况下, 必须保证该对象资源的生命周期必须比异步函数的生命周期要长, 因为如果在异步函数执行的过程中它所用的对象被其他线程析构了, 那么会造成程序崩溃. 因此我们必须使用一些方法保证对象资源的生命周期, 一般实践中使用对象的this
指针来传递对象的上下文, 保证该对象的跨线程生命周期. 简单来说, 我们使用this
指针保证A
线程中的对象资源在调用B
线程中的异步方法, 且该方法使用该对象资源时, 该对象资源的生命周期必须长于B
线程中的异步函数, 即其在B
线程中异步函数生命周期中不可被其他线程释放.
为了实现上述需求, 我们可以使用shared_ptr
来管理this
指针(管理对象资源). 那么该如何管理呢?
我们考虑从该对象本身构造出指向其自身的shared_ptr
对象. 我们需要从shared_ptr
所管理的对象中获取其指向自身的shared_ptr
对象时, 如果我们简单的使用类的成员函数返回指向自身的shared_ptr
对象, 那么根据以上分析, 这将会导致程序出错.
1 |
|
上面错误使用场景和最开始提到的场景是一模一样的,即都用了多个Control Block
来管理指针资源, 从而导致重复释放. 这种情况下, 我们就需要enable_shared_from_this
来实现上述需求.
1 |
|
需要实现上述需求的资源类需要公有继承enable_shared_from_this
模板类, 然后使用shared_from_this
方法(enable_shared_from_this
模板类的公有方法)来获取一个指向其对象自身的shared_ptr
对象.
我们简单来看以下enable_shared_from_this
模板类的源码, 看看其是如何实现的上述功能的.
1 | /** |
以上源码我们发现三点:
enable_shared_from
存在一个mutable成员变量weak_ptr<_Tp> _M_weak_this
, 这样const
对象也能够对其进行修改.enable_shared_from
含有友元类__shared_ptr
, 这样shared_ptr
类能够访问enable_shared_from
的私有成员变量.cpp17
添加了weak_from_this()
方法返回weak_ptr
的拷贝, 然后使用weak_ptr.lock()
就能安全的获取shared_ptr
对象.
具体shared_from_this()
函数的实现是很简单的, 其通过私有成员变量来构造shared_ptr
对象然后返回. 那么该私有成员变量是何时初始化的呢?
它是在构造shared_ptr
对象的时候被初始化的, 在初始化构造一个shared_ptr
对象的时候, 可以根据type traits
(std::enable_if
和 std::is_convertible
)来实现. 如果这个资源类公有继承了std::enable_shared_from_this
模板类, 那么就将父类中的_M_weak_this
初始化绑定到创建出来的shared_ptr
对象上去(友元类的声明让其能够访问私有成员变量), 这样就实现了_M_weak_this
的安全初始化. 这种设计对于shared_ptr
模板类来说是侵入式的.
最后给出一个讲解很好博客的图示和代码.
1 | struct Article : std::enable_shared_from_this<Article> { |
需要注意的是, 公有继承的原因是_M_weak_this
的初始化是在shared_ptr
对象的构造函数中初始化的, 必须能够检测到该类是继承了enable_shared_from_this
基类的. 另外, 必须通过shared_ptr来调用对象的shared_from_this, 因为_M_weak_this
的初始化是在shared_ptr
对象的构造函数中进行的, 如果还没有shared_ptr
对象被构造, 那么调用shared_from_this()
使用_M_weak_this
来构造shared_ptr
会造成std::bad_weak_ptr
异常, 原因是_M_weak_this
还没有和某个Contorl Block
相关联. 当然如果使用cpp17
, 可以用weak_from_this()
来获取weak_ptr
自行lock()
并判断来保证安全, 不过还是建议统一先构造shared_ptr
对象, 再安全的使用shared_from_this
方法.
总结
当一个类需要”共享自己”的时候, enable_shared_from_this
模板类就是标准库提供的强大工具. 它可以安全的管理和构造我们所需的shared_ptr
对象, 不过还是有一些问题是需要注意的. 首先我们必须先构造shared_ptr
对象, 然后使用类的shared_from_this
方法, 因为必须保证enable_shared_from_this
基类的_M_weak_this
被初始化. 其次我们不能在类的构造函数中调用shared_from_this
, 因为此时_M_weak_this
可能还未初始化. 而且必须公有继承enable_shared_from_this
.
最后对象的生命周期管理是多线程(异步)编程中必须关注的一个问题, cpp
提供的智能指针和enable_shared_from_this
等模板类很友好的实现了我们的需求, 不过仍然需要注意一些坑点. 向往侯捷老师说的胸中自有丘壑的境界水平, 努力做到知其然知其所以然, 才能提升自身的视野和水平呀.
参考
[cpp]资源生命周期管理类:enable_shared_from_this
https://csjsss.github.io/2022/04/26/cpp/[cpp]资源生命周期管理类-enable-shared-from-this/